/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.commonmodel.engine.core.common;

import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.common.BefDtBeanUtil;
import com.inspur.edp.cef.core.data.DataUtils;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.ElementDefaultVauleType;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.designtime.api.element.GspEnumValue;
import com.inspur.edp.cef.designtime.api.util.MetadataUtil;
import com.inspur.edp.cef.entity.accessor.base.IAccessor;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IValueObjData;
import com.inspur.edp.cef.entity.entity.dynamicProp.DynamicPropSetImpl;
import com.inspur.edp.cef.spi.common.UdtManagerUtil;
import com.inspur.edp.cef.spi.entity.info.propertyinfo.UdtPropertyInfo;
import com.inspur.edp.cef.spi.entity.resourceInfo.builinImpls.CefEntityResInfoImpl;
import com.inspur.edp.cef.spi.util.ExpressionUtil;
import com.inspur.edp.commonmodel.engine.api.data.AssociationInfo;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonModel;
import com.inspur.edp.das.commonmodel.IGspCommonObject;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.svc.expression.api.IExpressionEvaluator;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import com.inspur.edp.udt.entity.ISimpleUdtData;

import java.math.BigDecimal;
import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import lombok.var;
import org.apache.commons.lang3.time.FastDateFormat;
//import org.assertj.core.util.DateUtil;
import org.springframework.util.StringUtils;

public class CMUtil {

    public static final Predicate<IGspCommonField> ElementIsCustomizedPredicate = item -> isCustomized(
            item);

    public static final Predicate<IGspCommonObject> NodeIsCustomizedPredicate = item -> isCustomized(
            item);

    public static boolean isCustomized(IGspCommonField element) {
        return element.getCustomizationInfo() != null && element.getCustomizationInfo()
                .isCustomized();
    }

    public static boolean isCustomized(IGspCommonObject node) {
        return node.getCustomizationInfo() != null && node.getCustomizationInfo().isCustomized();
    }

    public static IGspCommonObject findNode(IGspCommonModel be, String childCode) {
        Objects.requireNonNull(be, "be");
        Objects.requireNonNull(childCode, "childCode");

        IGspCommonObject node = (IGspCommonObject) be.getAllObjectList().stream()
                .filter(item -> item.getCode().equalsIgnoreCase(childCode))
                .findFirst()
                .orElse(null);
        Objects.requireNonNull(node, "be" + be.getCode() + "上不存在子表" + childCode);
        return node;
    }

    public static IGspCommonObject findNode(IGspCommonObject node, String childCode) {
        Objects.requireNonNull(node, "node");
        Objects.requireNonNull(childCode, "childCode");

        IGspCommonObject childNode =
                (IGspCommonObject)
                        node.getContainChildObjects().stream()
                                .filter(item -> item.getCode().equalsIgnoreCase(childCode))
                                .findFirst()
                                .orElse(null);
        Objects.requireNonNull(childNode, "子表" + node.getCode() + "上不存在子表" + childCode);
        return childNode;
    }

    public static IGspCommonElement checkElementExists(IGspCommonObject node, String labelId,
                                                       Predicate<IGspCommonField> elementPredicate) {
        Objects.requireNonNull(labelId, "labelId");

        IGspCommonField element = findElement(node, labelId);
        if (element == null || (elementPredicate != null && !elementPredicate.test(element))) {
            throwElementNotFound(node, labelId);
        }
        return (IGspCommonElement) element;
    }

    public static boolean isElementExist(IGspCommonObject node, String labelId,
                                         Predicate<IGspCommonField> elementPredicate) {
        IGspCommonField element = findElement(node, labelId);
        return element != null && (elementPredicate == null || elementPredicate.test(element));
    }

    private static IGspCommonElement findElement(IGspCommonObject node, String labelId) {
        return (IGspCommonElement) node.getContainElements().getByLabelId(labelId);
    }

    public static IGspCommonElement checkElementValue(IGspCommonObject node, String labelId,
                                                      Object value, Predicate<IGspCommonField> elementPredicate) {
        IGspCommonElement element = checkElementExists(node, labelId, elementPredicate);

        if (element.getIsUdt()) {
            if (value != null && !(value instanceof IValueObjData)) {
                throwInvalidPropertyValue(node, element, value);
            }
        } else if (element.getObjectType() == GspElementObjectType.Association) {
            if (value != null) {
                if (value instanceof AssociationInfo) {
                    GspAssociation association = element.getChildAssociations().get(0);
                    ((AssociationInfo) value).setAssociation(association);
                    for (Map.Entry<String, Object> key : ((AssociationInfo) value).getValues()
                            .entrySet()) {
                        checkRefElementExists(association, key.getKey());
                        //TODO:check key.getValue()
                    }
                } else {
                    throwInvalidPropertyValue(node, element, value);
                }
            }
        }
        return element;
        //TODO: check enum/normal
    }

    private static void throwInvalidPropertyValue(IGspCommonObject node, IGspCommonElement element,
                                                  Object value) {
        throw new RuntimeException(
                (value != null ? value.getClass() : "null") + " is not valid for" + node
                        .getCode() + "." + element.getLabelID());
    }

    public static IGspCommonObject checkNodeExists(IGspCommonObject node, String childCode,
                                                   Predicate<IGspCommonObject> predicate) {
        Objects.requireNonNull(childCode, "childCode");

        IGspCommonObject element = null;
        for (IGspCommonObject child : node.getContainChildObjects()) {
            if (child.getCode().equalsIgnoreCase(childCode)) {
                element = child;
                break;
            }
        }
        if (element == null || (predicate != null && !predicate.test(element))) {
            throwNodeNotFound(node, childCode);
        }
        return element;
    }

    public static IGspCommonElement checkRefElementExists(
            GspAssociation association, String labelId) {
        if (association.getBelongElement().getLabelID().equalsIgnoreCase(labelId)) {
            return (IGspCommonElement) association.getBelongElement();
        }
        IGspCommonElement field =
                (IGspCommonElement)
                        association.getRefElementCollection().stream()
                                .filter(item -> item.getLabelID().equalsIgnoreCase(labelId))
                                .findFirst()
                                .orElse(null);
        if (field == null) {
            throwRefElementNotFound(association, labelId);
        }
        return field;
    }

    public static <TKey, TValue> HashMap<TKey, TValue> cloneForValues(HashMap<TKey, TValue> source)
            throws CloneNotSupportedException {
        if (source == null) {
            return null;
        }
        HashMap<TKey, TValue> result = new HashMap<>(source.size(), 1);
        for (Map.Entry<TKey, TValue> pair : source.entrySet()) {
            Object value = null;
            if (pair.getValue() == null) {
                value = null;
            } else if (pair.getValue() instanceof AssociationInfo) {
                value = ((AssociationInfo) pair.getValue()).clone();
            } else if (pair.getValue() instanceof ICefData) {
                value = ((ICefData) pair.getValue()).copySelf();
            } else {
                value = pair.getValue();
            }
            result.put(pair.getKey(), (TValue) value);
        }
        return result;
    }

    public static Stream<IGspCommonField> streamElements(IGspCommonObject node,
                                                         Predicate<IGspCommonField> predicate) {
        Stream<IGspCommonField> rez = node.getContainElements().stream();
        if (predicate != null) {
            rez = rez.filter(predicate);
        }
        return rez;
    }

    public static String getUdtConfigId(String udtId, String udtPackgeName) {
        GspMetadata udtMetadata;
        try {
            udtMetadata = MetadataUtil.getCustomRTMetadata(udtId);
        } catch (Exception e) {
            throw new RuntimeException("获取Udt元数据失败" + udtId, e);
        }
        var udtContent = (UnifiedDataTypeDef) (udtMetadata != null ? udtMetadata.getContent() : null);
        if (udtContent == null) {
            throw new RuntimeException("udt元数据不存在" + udtId);
        }
        return udtContent.getUdtType();
    }

    public static void initValue(IGspCommonObject node, IEntityData data,
                                 Predicate<IGspCommonField> predicate) {
        initValue(node, data, predicate, null);
    }

    public static void initValue(IGspCommonObject node, IEntityData data,
                                 Predicate<IGspCommonField> predicate, CefEntityResInfoImpl resInfo) {
        for (IGspCommonField field : node.getContainElements()) {
            if (predicate != null && !predicate.test(field)) {
                continue;
            }

            if (node.getIDElement() == field) {
                continue;
            }

            if (field.getIsUdt()) {
                String udtConfigId = getUdtConfigId(field, resInfo);
                data.setValue(field.getLabelID(),
                        UdtManagerUtil.getUdtFactory().createManager(udtConfigId).createDataType());
                continue;
            }

            switch (field.getObjectType()) {
                case None://普通字段不处理
                    break;
                case Enum:
                    GspEnumValue defaultValue = field.getContainEnumValues().stream()
                            .filter(item -> item.getIsDefaultEnum()).findFirst()
                            .orElse(field.getContainEnumValues().get(0));
                    data.setValue(field.getLabelID(), defaultValue.getValue());
                    break;
                case DynamicProp:
                    data.setValue(field.getLabelID(), new DynamicPropSetImpl());
                    break;
                case Association:
                    if (field.getObjectType() == GspElementObjectType.Association
                            && field.getChildAssociations() != null &&
                            !field.getChildAssociations().isEmpty()) {
                        data.setValue(field.getLabelID(),
                                new AssociationInfo(field.getChildAssociations().get(0)));
                    }
                    break;
            }
        }
    }

    @Deprecated
    public static void accInitNestedValue(IAccessor acc, IGspCommonObject node,
                                          Map<String, Object> values, Predicate<IGspCommonField> predicate) {
        accInitNestedValue(acc, node, values, predicate, null);
    }

    public static void accInitNestedValue(IAccessor acc, IGspCommonObject node,
                                          Map<String, Object> values, Predicate<IGspCommonField> predicate, CefEntityResInfoImpl resInfo) {
        CMUtil.streamElements(node, predicate)
                .forEach(item -> {
                    if (item.getIsUdt()) {
                        String configId = getUdtConfigId(item, resInfo);
                        values.put(item.getLabelID(),
                                DataUtils.createAccNested(acc, configId, item.getLabelID(), acc.getIsReadonly()));
                    } else if (item.getObjectType() == GspElementObjectType.DynamicProp) {
                        values.put(item.getLabelID(),
                                DataUtils.createDynPropSetAcc(acc, item.getLabelID(), acc.getIsReadonly()));
                    }
                });
    }

    public static String getUdtConfigId(IGspCommonField field, CefEntityResInfoImpl resInfo) {
        if (resInfo != null && resInfo.getEntityTypeInfo().getPropertyInfo(field.getLabelID()) != null)
            return ((UdtPropertyInfo) resInfo.getEntityTypeInfo().getPropertyInfo(field.getLabelID()).getObjectInfo()).getUdtConfigId();
        return getUdtConfigId(field.getUdtID(), field.getUdtPkgName());
    }

    public static void assignDefaultValue(IGspCommonObject node, IEntityData data) {
        for (IGspCommonField field : node.getContainElements()) {
            try {
                if (field.getIsUdt() || StringUtils.isEmpty(field.getDefaultValue())) {
                    continue;
                }

                switch (field.getObjectType()) {
                    case Enum:
//          field.getContainEnumValues().stream().findAny(item -> item.).con
                        data.setValue(field.getLabelID(), field.getDefaultValue());
                        break;
                    case None:
                        assignDefaultValue_NoneObjType(field, data);
                        break;
                    case Association:
                        throw new RuntimeException(
                                "不支持将默认值 [" + field.getDefaultValue() + "] 设置在关联字段:" + field.getLabelID());
                }
                // TODO: 动态属性初始值
            } catch (Exception e) {
                throw new RuntimeException(String
                        .format("ID编号名称分别为[%s][%s][%s]的模型的节点[%s]的字段[%s]上设置的默认值[%s]格式不正确, 请联系元数据开发人员修改.",
                                node.getBelongModel().getID(), node.getBelongModel().getCode(),
                                node.getBelongModel().getName(), node.getCode(), field.getCode(),
                                field.getDefaultValue()));
            }
        }
    }

    public static void assignDefaultValue_NoneObjType(IGspCommonField field, IEntityData data) {
        assignChildDefaultValue_NoneObjType(field, data, null);
    }

    public static void assignChildDefaultValue_NoneObjType(IGspCommonField field, IEntityData data, Map<String, String> expressionValueMap) {
        if (field.getMDataType() == null) {
            return;
        }
        if (field.getDefaultValueType() == ElementDefaultVauleType.Expression) {
            Object tempValue = null;
            tempValue = ExpressionUtil.getExpressionValue(field.getDefaultValue(), expressionValueMap);
            tempValue = tempValue == null ? "" : tempValue;
            setTransDefaultValue(data, field, tempValue);
        } else {
            setTransDefaultValue(data, field, field.getDefaultValue());
        }

    }

    private static void setTransDefaultValue(IEntityData data, IGspCommonField field, Object value) {
        switch (field.getMDataType()) {
            case String:
            case Text:
                value = value.toString();
                break;
            case Integer:
                value = Integer.valueOf(value.toString());
                break;
            case Decimal:
                value = new BigDecimal(value.toString());
                break;
            case Boolean:
                value = Boolean.valueOf(value.toString());
                break;
            case Date:
                if (value instanceof String) {
                    value = Date.valueOf(value.toString());
                }
                break;
            case DateTime:
                if (value instanceof String) {
                    value = transDate(value);
                }
                break;
            case Binary:
                throw new RuntimeException(
                        "不支持将默认值 [" + field.getDefaultValue() + "] 设置在二进制字段:" + field.getLabelID());
            default:
                throw new RuntimeException(
                        "元数据字段：[(" + field.getLabelID() + ")" + field.getName() + "]的数据类型:" + field.getMDataType() + "错误");
        }
        data.setValue(field.getLabelID(), value);
    }

    private static Object transDate(Object tempValue) {
        Object value = null;
        if (tempValue == null) {
            return value;
        }
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            value = formatter.parse(tempValue.toString());
        } catch (ParseException e) {
            throw new RuntimeException("时间日期类型数据格式转换出错！", e);
        }
        return value;
    }

    public static void throwNodeNotFound(IGspCommonObject node, String childCode) {
        throw new RuntimeException("找不到节点" + node.getCode() + "." + childCode);
    }

    public static void throwElementNotFound(IGspCommonObject node, String labelId) {
        throw new RuntimeException("找不到字段" + node.getCode() + "." + labelId);
    }

    public static void throwMgrActionNotFound(IGspCommonModel be, String actionCode) {
        throw new RuntimeException("找不到自定义动作" + be.getCode() + "." + actionCode);
    }

    public static void throwRefElementNotFound(GspAssociation association, String labelId) {
        throw new RuntimeException(
                "关联带出字段不存在" + association.getBelongElement().getLabelID() + "." + labelId);
    }

    public static String convert2String(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            return (String) value;
        }
        if (value instanceof AssociationInfo) {
            return ((AssociationInfo) value).getValue();
        }
        if (value instanceof ISimpleUdtData) {
            return (String) ((ISimpleUdtData) value).getValue();
        }
        throw new RuntimeException();
    }

    public static List<String> buildNodePropertyNames(IGspCommonObject node, Predicate<IGspCommonField> predicate) {
        return streamElements(node, predicate).map(item -> item.getLabelID()).collect(Collectors.toList());
    }
}
